<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <title>Tutorial Demo</title>

  <style>
    @import './iconfont/iconfont.css';
  </style>
</head>
<body>

<!-- 预先加载字体, 以防图渲染时字体还未下载, 后续可以隐藏 -->
<span class="iconfont">&#xe895;</span>

<!-- 图的画布容器 -->
<div id="mountNode"></div>

<span class="iconfont">&#xe894;</span>

<!-- 引入 G6 -->
<script src="https://gw.alipayobjects.com/os/lib/antv/g6/4.7.0/dist/g6.min.js"></script>

<script>

  // 缩略图 (Minimap) 是一种常见的用于快速预览和探索图的工具，可作为导航辅助用户探索大规模图。
  // 实例化 minimap 插件
  const minimap = new G6.Minimap({
    size: [100, 100],
    className: 'minimap',
    type: 'delegate',
  });

  // 网格可用于辅助用户在拖拽节点时对齐到网格
  // 实例化 grid 插件
  const grid = new G6.Grid();

  //图渲染iconfont方法
  G6.registerNode('iconfont', {
    draw(cfg, group) {
      const { backgroundConfig: backgroundStyle, style, labelCfg: labelStyle } = cfg;

      if (backgroundStyle) {
        group.addShape('circle', {
          attrs: {
            x: 0,
            y: 0,
            r: cfg.size,
            ...backgroundStyle,
          },
          // must be assigned in G6 3.3 and later versions. it can be any value you want
          name: 'circle-shape',
        });
      }

      const keyShape = group.addShape('text', {
        attrs: {
          x: 0,
          y: 0,
          fontFamily: 'iconfont', // 对应css里面的font-family: "iconfont";
          textAlign: 'center',
          textBaseline: 'middle',
          text: cfg.text,
          fontSize: cfg.size * 1.2,
          ...style,
        },
        // must be assigned in G6 3.3 and later versions. it can be any value you want
        name: 'text-shape1',
      });
      const labelY = backgroundStyle ? cfg.size * 2 : cfg.size;

      group.addShape('text', {
        attrs: {
          x: 0,
          y: labelY,
          textAlign: 'center',
          text: cfg.label,
          ...labelStyle.style,
        },
        // must be assigned in G6 3.3 and later versions. it can be any value you want
        name: 'text-shape1',
      });
      return keyShape;
    },
  });

  const COLOR = '#40a9ff';
  // 创建 G6 图实例
  const graph = new G6.Graph({
    container: 'mountNode', // 指定图画布的容器 id，与第 9 行的容器对应
    // 画布宽高
    width: 800,
    height: 500,
    modes: {
      default: ['drag-node', 'drag-canvas', 'zoom-canvas',
        // 点提示框交互工具的配置
        {
          type: 'tooltip',
          formatText(model) {
            const text = 'label: ' + model.label
              + '<br/> class: ' + model.class;
            return text;
          },
          shouldUpdate: e => {
            return true;
          }
        },
        // 边提示框交互工具的配置
        {
          type: 'edge-tooltip',
          formatText(model) {
            const text = 'source: ' + model.source
              + '<br/> target: ' + model.target
              + '<br/> weight: ' + model.weight;
            return text;
          },
          shouldUpdate: e => {
            return true;
          }
        }
      ],
    },
    fitViewPadding: [20, 40, 50, 20],
    defaultNode: {
      backgroundConfig: {
        backgroundType: 'circle',
        fill: COLOR,
        stroke: 'LightSkyBlue',
      },
      type: 'iconfont',
      size: 16,
      style: {
        fill: '#fff',
        stroke: '#666', // 节点描边色
        lineWidth: 1, // 节点描边粗细
      },
      labelCfg: {
        style: {
          fill: COLOR,
          fontSize: 16,
        },
      },
    },

    // 边在默认状态下的样式配置（style）和其他配置
    defaultEdge: {
      // ...                 // 边的其他配置
      //type: 'quadratic',   // 二阶贝塞尔曲线
      // 边样式配置
      style: {
        opacity: 0.6, // 边透明度
        stroke: 'grey', // 边描边颜色
        endArrow: {
          // 使用内置箭头路径函数，参数为箭头的 宽度、长度、偏移量（默认为 0，与 d 对应）
          path: G6.Arrow.triangle(7, 7, 0),
          d: 0
        },
      },
      // 边上的标签文本配置
      labelCfg: {
        autoRotate: true, // 边上的标签文本根据边的方向旋转
      },
    },

    //图布局 Layout
    layout: {
      // Object，可选，布局的方法及其配置项，默认为 random 布局。
      // type: 'force', // 指定为力导向布局
      type: 'force2', // 指定为力导向布局
      //animate: true, // 是否每次迭代都刷新画布, force需开启避免页面有卡顿感, force2开启后抖动过于频繁
      preventOverlap: true, // 防止节点重叠
      // nodeSize: 30        // 节点大小，用于算法中防止节点重叠时的碰撞检测。由于已经在上一节的元素配置中设置了每个节点的 size 属性，则不需要在此设置 nodeSize。
      linkDistance: 50, // 指定边距离
    },

    // 交互状态 State
    // 节点不同状态下的样式集合
    // 样式 + 点击事件绑定 => 点击图后样式变化
    nodeStateStyles: {
      // 鼠标 hover 上节点，即 hover 状态为 true 时的样式
      hover: {
        fill: '#000',
      },
      // 鼠标点击节点，即 click 状态为 true 时的样式
      click: {
        stroke: '#000',
        lineWidth: 100,
      },
    },
    // 边不同状态下的样式集合
    edgeStateStyles: {
      // 鼠标点击边，即 click 状态为 true 时的样式
      click: {
        stroke: 'steelblue',
        //stroke: '#CCE8CF',
      },
    },

    plugins: [minimap, grid], // 将 minimap, grid 实例配置到图上

  });



  // 定义数据源
  const data = {
    "nodes": [
      //案例1
      {"id": "01", "label": "人01", "class": "c0"},
      {"id": "02", "label": "人02", "class": "c0"},
      {"id": "03", "label": "人03", "class": "c0"},
      {"id": "04", "label": "人04", "class": "c0"},
      {"id": "05", "label": "人05", "class": "c0"},
      {"id": "11", "label": "企11", "class": "c1"},
      {"id": "12", "label": "企12", "class": "c1"},
      {"id": "13", "label": "企13", "class": "c1"},
      {"id": "14", "label": "企14", "class": "c1"},
      //案例2
      {"id": "21", "label": "人21", "class": "c0"},
      {"id": "22", "label": "人22", "class": "c0"},
      {"id": "23", "label": "人23", "class": "c0"},
      {"id": "24", "label": "人24", "class": "c0"},
      {"id": "31", "label": "非01", "class": "c2"},
      {"id": "41", "label": "企41", "class": "c1"},
      {"id": "42", "label": "企42", "class": "c1"},
      {"id": "43", "label": "企43", "class": "c1"},
      {"id": "44", "label": "企44", "class": "c1"},
    ],
    "edges": [
      //案例1
      {"source": "01", "target": "14", "label": "0.50", "weight": 0.50 },
      {"source": "02", "target": "14", "label": "0.50", "weight": 0.50 },
      {"source": "14", "target": "13", "label": "0.50", "weight": 0.50 },
      {"source": "03", "target": "13", "label": "0.25", "weight": 0.25 },
      {"source": "12", "target": "13", "label": "0.25", "weight": 0.25 },
      {"source": "13", "target": "12", "label": "0.50", "weight": 0.50 },
      {"source": "04", "target": "12", "label": "0.50", "weight": 0.50 },
      {"source": "12", "target": "11", "label": "0.50", "weight": 0.50 },
      {"source": "05", "target": "11", "label": "0.50", "weight": 0.50 },
      //案例2
      {"source": "21", "target": "44", "label": "0.50", "weight": 0.50 },
      {"source": "22", "target": "44", "label": "0.50", "weight": 0.50 },
      {"source": "44", "target": "43", "label": "0.50", "weight": 0.50 },
      {"source": "23", "target": "43", "label": "0.25", "weight": 0.25 },
      {"source": "31", "target": "43", "label": "0.25", "weight": 0.25 },
      {"source": "43", "target": "42", "label": "0.50", "weight": 0.50 },
      {"source": "24", "target": "42", "label": "0.50", "weight": 0.50 },
      {"source": "42", "target": "41", "label": "0.50", "weight": 0.50 },
      {"source": "31", "target": "41", "label": "0.50", "weight": 0.50 },
    ]
  }

  //两节点间存在多条边, 数据集处理
  G6.Util.processParallelEdges(data.edges);

  // 可以直接将配置写入数据文件(后端处理)；也可以在读入数据后，通过遍历的方式写入配置(前端处理)。
  // 遍历点数据
  data.nodes.forEach((node) => {
    if (!node.style) {
      node.style = {};
    }
    switch (
      node.class // 根据节点数据中的 class 属性配置图形
      ) {
      case 'c0': {
        node.text = '\ue6b0';
        break;
      }
      case 'c1': {
        node.text = '\ue895';
        break;
      }
      case 'c2': {
        node.text = '\ue894';
        break;
      }
    }
  });

  // 遍历边数据
  data.edges.forEach((edge) => {
    if (!edge.style) {
      edge.style = {};
    }
    edge.style.lineWidth = edge.weight * 4; // 边的粗细映射边数据中的 weight 属性数值
    // opacity 文本透明度
    edge.style.opacity = 0.6;
    edge.style.stroke = 'grey';
  });

  // 读取数据
  graph.data(data);
  // 渲染图
  graph.render();


  // 点击事件绑定
  // 监听鼠标进入节点
  graph.on('node:mouseenter', (e) => {
    const nodeItem = e.item;
    // 设置目标节点的 hover 状态 为 true
    graph.setItemState(nodeItem, 'hover', true);
  });
  // 监听鼠标离开节点
  graph.on('node:mouseleave', (e) => {
    const nodeItem = e.item;
    // 设置目标节点的 hover 状态 false
    graph.setItemState(nodeItem, 'hover', false);
  });
  // 监听鼠标点击节点
  graph.on('node:click', (e) => {
    // 1. 清理之前点击过的节点状态
    // 先将所有当前有 click 状态的节点的 click 状态置为 false
    const clickNodes = graph.findAllByState('node', 'click');
    clickNodes.forEach((cn) => {
      graph.setItemState(cn, 'click', false);
    });

    // 2. 当前节点状态修改
    const nodeItem = e.item;
    // 设置目标节点的 click 状态 为 true
    graph.setItemState(nodeItem, 'click', true);
  });
  // 监听鼠标点击节点
  graph.on('edge:click', (e) => {
    // 先将所有当前有 click 状态的边的 click 状态置为 false
    const clickEdges = graph.findAllByState('edge', 'click');
    clickEdges.forEach((ce) => {
      graph.setItemState(ce, 'click', false);
    });
    const edgeItem = e.item;
    // 设置目标边的 click 状态 为 true
    graph.setItemState(edgeItem, 'click', true);
  });

  // 在 graph.render() 之后调用以下语句：重新渲染字体,避免没有加载到
  /*setTimeout(() => {
    graph.paint();
  }, 500);*/

</script>
</body>
</html>
